package flare.query
{
  import flare.util.IEvaluable;
  import flare.util.IPredicate;

  /**
   * Base class for query expression operators. Expressions are organized
   * into a tree of operators that perform data processing or predicate
   * testing on input <code>Object</code> instances.
   */
  public class Expression implements IEvaluable, IPredicate
  {
    /**
     * Evaluates this expression with the given input object.
     * @param o the input object to this expression
     * @return the result of evaluating the expression
     */
    public function eval(o:Object = null):*
    {
      return o;
    }

    /**
     * Boolean predicate that tests the output of evaluating this
     * expression. Returns true if the expression evaluates to true, or
     * a non-null or non-zero value. Returns false if the expression
     * evaluates to false, or a null or zero value.
     * @param o the input object to this expression
     * @return the Boolean result of evaluating the expression
     */
    public function predicate(o:Object):Boolean
    {
      return Boolean(eval(o));
    }

    /**
     * The number of sub-expressions that are children of this expression.
     * @return the number of child expressions.
     */
    public function get numChildren():int
    {
      return 0;
    }

    /**
     * Returns the sub-expression at the given index.
     * @param idx the index of the child sub-expression
     * @return the requested sub-expression.
     */
    public function getChildAt(idx:int):Expression
    {
      return null;
    }

    /**
     * Set the sub-expression at the given index.
     * @param idx the index of the child sub-expression
     * @param expr the sub-expression to set
     * @return true if the the sub-expression was successfully set,
     *  false otherwise
     */
    public function setChildAt(idx:int, expr:Expression):Boolean
    {
      return false;
    }

    /**
     * Returns a string representation of the expression.
     * @return this expression as a string value
     */
    public function toString():String
    {
      return null;
    }

    /**
     * Creates a cloned copy of the expression. Recursively clones any
     * sub-expressions.
     * @return the cloned expression.
     */
    public function clone():Expression
    {
      throw new Error("This is an abstract method");
    }

    /**
     * Sequentially invokes the input function on this expression and all
     * sub-expressions. Complete either when all expressions have been
     * visited or the input function returns true, thereby signalling an
     * early exit.
     * @param f the visiting function to invoke on this expression
     * @return true if the input function signalled an early exit
     */
    public function visit(f:Function):Boolean
    {
      var iter:ExpressionIterator = new ExpressionIterator(this);
      return visitHelper(iter, f);
    }

    private function visitHelper(iter:ExpressionIterator, f:Function):Boolean
    {
      if(f(iter.current) as Boolean)return true;

      if(iter.down() != null)
      {
        do
        {
          if(visitHelper(iter, f))return true;
        } while(iter.next() != null);
        iter.up();
      }
      return false;
    }

    // --------------------------------------------------------------------

    private static const _LBRACE:Number = "{".charCodeAt(0);
    private static const _RBRACE:Number = "}".charCodeAt(0);
    private static const _SQUOTE:Number = "'".charCodeAt(0);
    private static const _DQUOTE:Number = "\"".charCodeAt(0);

    /**
     * Utility method that maps an input value into an Expression. If the
     * input value is already an Expression, it is simply returned. If the
     * input value is a String, it is interpreted as either a variable or
     * string literal. If the first and last characters of the string are
     * single quotes (') or double quotes ("), the characters between the
     * quotes will be used as a string Literal. If there are no quotes, or
     * if the string is enclosed by curly braces ({}), the string value
     * (sans braces) will be used as the property name of a new Variable.
     * In all other cases (Numbers, Dates, etc), a new Literal expression
     * for the input value is returned.
     * @param o the input value
     * @return an Expression corresponding to the input value
     */
    public static function expr(o:*):Expression
    {
      if(o is Expression)
      {
        return o as Expression;
      }

      else if(o is Class)
      {
        return new IsA(Class(o));
      }
      else if(o is String)
      {
        var s:String = o as String;
        var c1:Number = s.charCodeAt(0);
        var c2:Number = s.charCodeAt(s.length - 1);

        if(c1 == _LBRACE && c2 == _RBRACE)
        { // braces -> variable
          return new Variable(s.substr(1, s.length - 2));
        }

        else if(c1 == _SQUOTE && c2 == _SQUOTE)
        { // quote -> string
          return new Literal(s.substr(1, s.length - 2));
        }

        else if(c1 == _DQUOTE && c2 == _DQUOTE)
        { // quote -> string
          return new Literal(s.substr(1, s.length - 2));
        }

        else
        { // default -> variable
          return new Variable(s);
        }
      }

      else
      {
        return new Literal(o);
      }
    }
  } // end of class Expression
}